package com.dimples.dd.security.core.util;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.dimples.dd.common.exception.BizExceptionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.dimples.dd.common.result.ResultCode.REQUEST_PATH_DUPLICATION;

/**
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2024/11/6
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AnnotationUtil {
    private static final String VALUE = "value";

    /**
     * 获取指定包下所有添加了执行注解的方法信息
     *
     * @param classPath          包名
     * @param tagAnnotationClass 指定注解类型
     * @param <T>                <T>
     * @return Map
     */
    public <T> Map<String, Map<String, Object>> parseTagAnnotationUrlByClasspath(String classPath, Class<T> tagAnnotationClass) {
        Map<String, Map<String, Object>> resMap = new HashMap<>();

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

        String pattern = resolver.CLASSPATH_ALL_URL_PREFIX + classPath;

        MetadataReaderFactory factory = new CachingMetadataReaderFactory(resolver);
        Resource[] resources;
        try {
            resources = resolver.getResources(pattern);

            for (org.springframework.core.io.Resource r : resources) {
                MetadataReader reader = factory.getMetadataReader(r);
                resMap.putAll(resolveClass(reader, resMap, tagAnnotationClass));
            }
        } catch (Exception e) {
            log.error("注解解析错误: {}", ExceptionUtil.stacktraceToString(e));
        }

        return resMap;
    }

    private <T> Map<String, Map<String, Object>> resolveClass(MetadataReader reader, Map<String, Map<String, Object>> resMap, Class<T> tagAnnotationClass) throws Exception {
        String tagAnnotationClassCanonicalName = tagAnnotationClass.getCanonicalName();
        //获取注解元数据
        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();

        //获取类中RequestMapping注解的属性
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(RequestMapping.class.getCanonicalName());

        //若类无RequestMapping注解
        if (annotationAttributes == null) {
            return resMap;
        }

        //获取RequestMapping注解的value
        String[] pathParents = (String[]) annotationAttributes.get(VALUE);
        if (0 == pathParents.length) {
            return resMap;
        }

        //获取RequestMapping注解的value
        StringBuilder pathParent = new StringBuilder(pathParents[0]);

        //获取当前类中已添加要扫描注解的方法
        Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(tagAnnotationClassCanonicalName);

        Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
        boolean allFlag = false;
        if (annotationTypes.contains(tagAnnotationClassCanonicalName)) {
            annotatedMethods = getMethodMetadata(annotationMetadata);
            allFlag = true;
        }

        for (MethodMetadata annotatedMethod : annotatedMethods) {
            //获取当前方法中要扫描注解的属性
            Map<String, Object> targetAttr = annotatedMethod.getAnnotationAttributes(tagAnnotationClassCanonicalName);
            //获取当前方法中要xxxMapping注解的属性
            Map<String, Object> mappingAttr = getPathByMethod(annotatedMethod);
            if (mappingAttr == null) {
                continue;
            }

            String[] childPath = (String[]) mappingAttr.get(VALUE);
            if (targetAttr == null || childPath == null || childPath.length == 0) {
                if (!allFlag) {
                    continue;
                }
            }

            String s = ArrayUtil.get(childPath, 0);
            boolean equals = s.charAt(0) == '/';
            boolean pathParentEquals = pathParent.substring(0, 1).equals("/");
            if (!pathParentEquals) {
                pathParent.insert(0, "/");
            }
            String path;
            if (equals) {
                path = pathParent.toString() + ArrayUtil.get(childPath, 0);
            } else {
                path = pathParent + "/" + ArrayUtil.get(childPath, 0);
            }

            boolean isHas = resMap.containsKey(path);
            if (isHas) {
                throw BizExceptionUtil.exception(REQUEST_PATH_DUPLICATION);
            }
            resMap.put(path, targetAttr);
        }

        return resMap;
    }

    private Set<MethodMetadata> getMethodMetadata(AnnotationMetadata annotatedMethod) {

        Set<MethodMetadata> annotatedMethods = new HashSet<>();
        Set<MethodMetadata> annotatedMethodsGet =
                annotatedMethod.getAnnotatedMethods(GetMapping.class.getCanonicalName());

        Set<MethodMetadata> annotatedMethodsPost =
                annotatedMethod.getAnnotatedMethods(PostMapping.class.getCanonicalName());
        Set<MethodMetadata> annotatedMethodsDelete =
                annotatedMethod.getAnnotatedMethods(DeleteMapping.class.getCanonicalName());

        Set<MethodMetadata> annotatedMethodsPut =
                annotatedMethod.getAnnotatedMethods(PutMapping.class.getCanonicalName());

        Set<MethodMetadata> annotatedMethodsRequest =
                annotatedMethod.getAnnotatedMethods(RequestMapping.class.getCanonicalName());
        annotatedMethods.addAll(annotatedMethodsGet);
        annotatedMethods.addAll(annotatedMethodsPost);
        annotatedMethods.addAll(annotatedMethodsDelete);
        annotatedMethods.addAll(annotatedMethodsPut);
        annotatedMethods.addAll(annotatedMethodsRequest);

        return annotatedMethods;
    }

    private Map<String, Object> getPathByMethod(MethodMetadata annotatedMethod) {
        Map<String, Object> annotationAttributes =
                annotatedMethod.getAnnotationAttributes(GetMapping.class.getCanonicalName());
        if (annotationAttributes != null && annotationAttributes.get(VALUE) != null) {
            return annotationAttributes;
        }
        annotationAttributes = annotatedMethod.getAnnotationAttributes(PostMapping.class.getCanonicalName());
        if (annotationAttributes != null && annotationAttributes.get(VALUE) != null) {
            return annotationAttributes;
        }

        annotationAttributes = annotatedMethod.getAnnotationAttributes(DeleteMapping.class.getCanonicalName());
        if (annotationAttributes != null && annotationAttributes.get(VALUE) != null) {
            return annotationAttributes;
        }

        annotationAttributes = annotatedMethod.getAnnotationAttributes(PutMapping.class.getCanonicalName());
        if (annotationAttributes != null && annotationAttributes.get(VALUE) != null) {
            return annotationAttributes;
        }
        annotationAttributes = annotatedMethod.getAnnotationAttributes(RequestMapping.class.getCanonicalName());
        return annotationAttributes;
    }

}
